package uqbar.comm;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import uqbar.comm.protocol.InputBuilder;
import uqbar.comm.protocol.InputFactory;
import uqbar.comm.protocol.InputProcessor;
import uqbar.comm.protocol.Output;
import uqbar.comm.protocol.Protocol;
import uqbar.comm.protocol.SimpleProtocol;
import uqbar.comm.simpleProtocol.SimpleInput;


public class Connector<Input> implements PortListener {

	private InputBuilder remoteCallBuilder;
	private InputFactory<Input> inputFactory;
	private CompositeInputProcessor<Input> compositeInputProcessor = new CompositeInputProcessor<Input>();
	private Port port;
	private ExecutorService executor = Executors.newSingleThreadExecutor();

	public Port getPort() {
		if (this.port == null) {
			this.port = new uqbar.comm.SerialPort();
		}
		return this.port;
	}

	/**
	 * 
	 * @param commandFactory
	 * @param inputBuilder
	 */
	public Connector(final InputFactory<Input> inputFactory, final InputBuilder inputBuilder,
			final InputProcessor<Input>... inputProcessors) {

		this(inputFactory, inputBuilder, Arrays.asList(inputProcessors));
	}

	public Connector(final InputFactory<Input> inputFactory, final InputBuilder inputBuilder,
			final List<InputProcessor<Input>> inputProcessors) {
		this.inputFactory = inputFactory;
		this.remoteCallBuilder = inputBuilder;
		for (InputProcessor<Input> inputProcessor : inputProcessors) {
			this.addInputProcessor(inputProcessor);
		}
	}

	@SuppressWarnings("unchecked")
	public static Connector<SimpleInput> simple(final InputProcessor<SimpleInput> processor) {

		return forProtocol(new SimpleProtocol()).addInputProcessor(processor);
	}

	public static <T> Connector<T> forProtocol(final Protocol<T> protocol) {
		return new Connector<T>(protocol.newInputFactory(), protocol.newInputBuilder(), protocol.newInputProcessors());
	}

	public void setPort(final Port port) {
		this.port = port;
	}

	public Connector<Input> open() {
		try {
			this.port.open();
			this.port.addPortListener(this);
			return this;
		} catch (Exception e) {
			throw ProgramException.wrap(e);
		}
	}

	public void close() {
		this.port.close();
	}

	private InputFactory<Input> getInputCommandFactory() {
		return this.inputFactory;
	}

	public Connector addInputProcessor(final InputProcessor<Input> e) {
		this.compositeInputProcessor.add(e);
		return this;
	}

	public boolean containsInputProcessor(final InputProcessor<Input> o) {
		return this.compositeInputProcessor.contains(o);
	}

	public void removeInputProcessor(final InputProcessor<Input> o) {
		this.compositeInputProcessor.remove(o);
	}

	private InputBuilder getRemoteCallBuilder() {
		return this.remoteCallBuilder;
	}

	public void send(final Output command) {
		command.send(this.port.getOutputStream());
	}

	@Override
	public void dataAvailable(final Port port) {
		this.executor.execute(new Runnable() {

			@Override
			public void run() {
				Connector.this.getRemoteCallBuilder().read(port.getInputStream(),
						Connector.this.getInputCommandFactory(), Connector.this.compositeInputProcessor);

			}
		});
	}
}
